 import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.Timer;


/**
 * Creates an component that is a viewer onto an animation.
 *
 * @author Robert C. Duvall
 * Modified by Michael Elgart and Bruce Fu
 */
@SuppressWarnings("serial")
public class Canvas extends JComponent {
    /**
     * Int indicating that no key is pressed.
     */
    public static final int NO_KEY_PRESSED = -1;
    /**
     * Animate 25 times per second if possible (in milliseconds).
     */
    public static final int ONE_SECOND = 1000;
    /**
     * Number of times the game will be updated per second.
     */
    public static final int FRAMES_PER_SECOND = 25;
    /**
     * When changing size of screen, this is the increment up or down.
     */
    private static final int PIXEL_INCREMENT = 10;
    /**
     * Only one so that it maintains user's preferences.
     */
    private static final JFileChooser OURCHOOSER =
        new JFileChooser(System.getProperties().getProperty("user.dir"));

    // user's game to be animated
    //private Simulation arraylist to hold all simulations in the canvas;
    private ArrayList<Simulation> mySimulations;
    // drives simulation
    private Timer myTimer;
    // input state
    private int myLastKeyPressed;
    private Point myLastMousePosition;
    private int myLastMouseButtonPressed;
    private int mySimulationIndex = -1;


    /**
     * Initializes the canvas.
     *
     * @param size How large you want the canvas to be.
     */
    public Canvas (Dimension size) {
        // request component size
        setPreferredSize(size);
        // set component to receive user input
        setInputListeners();
        setFocusable(true);
        requestFocus();
        // create timer to drive the animation
        myTimer = new Timer(ONE_SECOND / FRAMES_PER_SECOND,
                new ActionListener() {
                    @Override
                    public void actionPerformed (ActionEvent e) {
                        step((double) FRAMES_PER_SECOND / ONE_SECOND);
                    }
                });
        // initialize simulation
        //myTarget = new Simulation(this);
        mySimulations = new ArrayList<Simulation>();
        loadModel();
    }

    /**
     * Starts the applet's action, i.e., starts the animation.
     */
    public void start () {
        myTimer.start();
    }

    /**
     * Take one step in the animation.
     * @param elapsedTime The amount of time between steps
     */
    public void step (double elapsedTime) {
        for (Simulation s : mySimulations) {
            s.update(elapsedTime);
        }
        //myTarget.update(elapsedTime);
        // indirectly causes paint to be called
        repaint();
    }

    /**
     * Stops the applet's action, i.e., the animation.
     */
    public void stop () {
        myTimer.stop();
    }

    /**
     * Returns the last key pressed by the player (or -1 if none pressed).
     *
     * @see java.awt.event.KeyEvent
     */
    public int getLastKeyPressed () {
        return myLastKeyPressed;
    }

    /**
     * Returns the last position of the mouse in the canvas.
     */
    public Point getLastMousePosition () {
        return myLastMousePosition;
    }

    /**
     * Paint the contents of the canvas.
     *
     * Never called by you directly, instead called by Java runtime
     * when area of screen covered by this container needs to be
     * displayed (i.e., creation, uncovering, change in status)
     *
     * @param pen used to paint shape on the screen
     */
    @Override
    public void paintComponent (Graphics pen) {
        pen.setColor(Color.WHITE);
        pen.fillRect(0, 0, getSize().width, getSize().height);
        for (Simulation s : mySimulations) {
            s.paint((Graphics2D) pen);
        }
        //myTarget.paint((Graphics2D) pen);
    }

    /**
     * Create listeners that will update state based on user input.
     */
    private void setInputListeners () {
        myLastKeyPressed = NO_KEY_PRESSED;
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed (KeyEvent e) {
                myLastKeyPressed = e.getKeyCode();
                manageSimulation(myLastKeyPressed);
                for (Simulation s: mySimulations) {
                    s.updateUserInput(myLastKeyPressed);
                }
            }
            @Override
            public void keyReleased (KeyEvent e) {
                myLastKeyPressed = NO_KEY_PRESSED;
            }
        });

        myLastMousePosition = new Point();
        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged (MouseEvent e) {
                myLastMousePosition = e.getPoint();
            }
        });

        myLastMouseButtonPressed = NO_KEY_PRESSED;
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed (MouseEvent e) {
                myLastMouseButtonPressed = e.getButton();
                double shortestDistance = Global.getCanvasDim().getHeight()
                    + Global.getCanvasDim().getWidth();
                for (Simulation s: mySimulations) {
                    double dist = s.findShortestDistance(myLastMousePosition);
                    if (dist <  shortestDistance) {
                        shortestDistance = dist;
                        mySimulationIndex = mySimulations.indexOf(s);
                    }
                }
                mySimulations.get(mySimulationIndex).addUserObjects(
                        myLastMousePosition);
            }

            @Override
            public void mouseReleased (MouseEvent e) {
                myLastMouseButtonPressed = NO_KEY_PRESSED;
                mySimulations.get(mySimulationIndex).deleteUserObjects();
            }
        });
    }

    private void loadModel () {
        final Factory factory = new Factory();
        Simulation newSimulation = new Simulation(this);
        int response = OURCHOOSER.showOpenDialog(null);
        if (response == JFileChooser.APPROVE_OPTION) {
            factory.loadModel(newSimulation, OURCHOOSER.getSelectedFile());
        }
        mySimulations.add(newSimulation);
    }

    private void manageSimulation (int keyCode) {
        switch (keyCode) {
            case KeyEvent.VK_SPACE:
                if (myTimer.isRunning()) {
                    stop();
                }
                else {
                    start();
                }
                break;
            case KeyEvent.VK_S:
                step((double) FRAMES_PER_SECOND / ONE_SECOND);
                break;
            case KeyEvent.VK_P:
                for (Simulation s : mySimulations) {
                    System.out.println(s);
                }
                //System.out.println(myTarget);
                break;
            case KeyEvent.VK_UP:
                increaseDimensions(true);
                break;
            case KeyEvent.VK_DOWN:
                increaseDimensions(false);
                break;
            case KeyEvent.VK_N:
                loadModel();
                break;
            case KeyEvent.VK_C:
                for (Simulation s : mySimulations) {
                    mySimulations.remove(s);
                }
                break;
            default:
                // good style
                break;
        }

    }


    private void increaseDimensions (boolean increase) {
        Dimension curr = this.getSize();
        if (increase) {
            this.setSize(curr.width + PIXEL_INCREMENT,
                    curr.height + PIXEL_INCREMENT);
        }
        else {
            this.setSize(curr.width - PIXEL_INCREMENT,
                    curr.height - PIXEL_INCREMENT);
        }
        Global.setCanvasCenter(curr);
        Global.setCanvasDim(curr);
    }
}
